#include "Parser.h"
#include "Scope.h"
#include "Prototypes.h"

#include <windows.h>

//#define DISPLAY_TOKENS

Symbol::Type Symbol::type(Token* n)
{
	return Unknown;
}

void Token::setType(Symbol::Type t)
{
	scope->setType(value, t);
}

void Token::setType(Token* n)
{
	scope->setType(value, n);
}

string Token::refType()
{
	return scope->getRefType(value);
}

string Symbol::toString(Type t, Token* n)
{
	switch(t)
	{
		case Integer:
			return "Integer";
		case Double:
			return "Real";
		case Var:
			return "Dynamic";
		case Unknown:
			return "Unknown";
		case Ref:
			return n->refType();
	}
	return "";
}

Symbol* symbol(const string& id, int bp = 0, Symbol* prototype = new Symbol()) 
{
	Symbol* s = symbol_table[id];
	if (s) {
		if (bp >= s->lbp) {
			s->lbp = bp;
		}
		delete prototype;
	} else {
		s = prototype;
		s->id = id;
		s->lbp = bp;
		symbol_table[id] = s;
	}
	return s;
}

Symbol* infix(const string& id,int bp, Symbol* prototype = new Infix()) 
{
	Symbol* s = symbol(id, bp, prototype);
	return s;
}

Symbol* infixr(const string& id,int bp, Symbol* prototype = new Infixr()) 
{
	Symbol* s = symbol(id, bp, prototype);
	return s;
}

Symbol* prefix(const string& id, Symbol* prototype = new Prefix()) {
	Symbol* s = symbol(id,0,prototype);
	return s;
}

Symbol* assignment(const string& id, Symbol* prototype = new Assignment())
{
	return infixr(id,10, prototype);
}

Symbol* constant(const string& id, const char* value, Symbol::Type t)
{
	Symbol* s = symbol(id,0, new Constant(value,t));
	return s;
}

Symbol* stmt(const string& id, Symbol* prototype)
{
	Symbol* s = symbol(id,0,prototype);
	return s;
}

Scope* new_scope()
{
	Scope* s = scope;
	scope = new Scope();
	scope->parent = s;
	return scope;
}

Token* advance(const string& id) 
{
	Token::Arity a;
	Symbol* o;

	if (!id.empty() && token->id() != id) 
	{
		token->error("Expected '" + id + "'.");
	}

	if (tokenizer.AtEnd())
	{
		token = new Token(symbol_table["(end)"]);
		return NULL;
	}

	SimpleToken* t = tokenizer.GetToken();
	if (!t) return NULL;
	
#ifdef DISPLAY_TOKENS
	cout << "{" << t->line << " " << t->value << " ";
#endif
	switch(t->type)
	{
	case SimpleToken::Name:
		o = symbol_table[t->value];
		a = Token::Name;
		if (!o)
		{
			o = symbol_table["(name)"];
#ifdef DISPLAY_TOKENS
			cout << "Identifier";
#endif
		}
#ifdef DISPLAY_TOKENS
		else cout << "Key Word";
#endif
		break;
	case SimpleToken::Operator:
		o = symbol_table[t->value];
		a = Token::Operator;
		if (!o) 
		{
			t->error("Unknown operator.");
		}
#ifdef DISPLAY_TOKENS
		cout << "Operator";
#endif
		break;
	case SimpleToken::String:
		o = String::Instance;
		a = Token::Literal;
#ifdef DISPLAY_TOKENS
		cout << "String";
#endif
		break;
	case SimpleToken::Char:
		o = String::Instance;
		a = Token::Literal;
#ifdef DISPLAY_TOKENS
		cout << "Char";
#endif
		break;
	case SimpleToken::Integer:
		o = Integer::Instance;
		a = Token::Literal;
#ifdef DISPLAY_TOKENS
		cout << "Integer";
#endif
		break;
	case SimpleToken::Float:
		o = Float::Instance;
		a = Token::Literal;
#ifdef DISPLAY_TOKENS
		cout << "Float";
#endif
		break;
	default:
		t->error("Unexpected token.");
	}
#ifdef DISPLAY_TOKENS
	cout << "} \n";
	advance();
	return NULL;
#endif
	
	token = new Token(o);

	token->value = t->value;
	token->arity = a;
	token->line = t->line;
	token->scope = scope;
	return token;
}

Token* expression(int rbp) 
{
	Token* t = token;
	advance();
	Token* left = t->nud();

	while (rbp < token->prototype->lbp) {
		t = token;
		advance();
		left = t->led(left);
	}
	return left;
}

Token* statement() {
	Token* n = token,* v;
	if (n->prototype->has_std || (n->prototype->id == "this" && !in_function)) {
		advance();
		return n->std();
	}
	v = expression(0);
	if (!v->prototype->assignment && v->id() != "(") {
		v->error("Bad expression statement.");
	}
	advance(";");
	return v;
}

Token* statements() 
{
	Token* a = new Token();
	Token* s;
	while (true) {
		if (token->id() == "}") {
			break;
		}
		s = statement();
		if (s) {
			a->push(s);
		}
	}
	return a;
}

void find_classes()
{
	Token* s;
	while (true) {
		if (token->id() == "}" || token->id() == "(end)") {
			break;
		}
		s = statement();
		if (s) {
			not_declared[s->value] = s;
		}
	}
}

void Parser::Search(Token* s,ofstream& compiled,string tab)
{
	compiled << tab << s->value << "|" << s->arity << "\n";
	if (!s->leaves)
	{
		if (s->first)
		{
			compiled << tab << "{" << "\n";
			Search(s->first,compiled, tab + "\t");
			if (s->second)
			{
				Search(s->second,compiled, tab + "\t");
				if (s->third)
				{
					Search(s->third,compiled, tab + "\t");
				}
			}
			compiled << tab << "}\n";
		}
	}
	else
	{
		compiled << tab << "{" << "\n";
		for (int i = 0; i < s->leaves->size(); i++)
		{
			Search((*s->leaves)[i],compiled, tab + "\t");
		}
		compiled << tab << "}\n";
	}
    
}

Parser::Parser(const vector<string>& files, const string& executable) : 
	m_files(files) ,
	m_exe(executable)
{
	scope = NULL;
	token = NULL;
	in_class = false;
	in_function = false;
	token_nr = 0;
	virtual_scope = new Scope();
	virtual_scope->defineFunction("toString",0);
	virtual_scope->defineFunction("toBool",0);
	virtual_scope->defineFunction("length",0);
	virtual_scope->defineFunction("isEmpty",0);
	virtual_scope->defineFunction("push",1);
	virtual_scope->defineFunction("pop",0);
	virtual_scope->defineFunction("onDraw",0);
	virtual_scope->defineFunction("draw",0);

	Scope* console_scope = new Scope();
	console_scope->defineStaticFunction("write",1);
	console_scope->defineStaticFunction("writeLine",1);
	console_scope->defineStaticFunction("readInt",0);
	console_scope->defineStaticFunction("readReal",0);
	console_scope->defineStaticFunction("readLine",0);
	console_scope->defineStaticFunction("readChar",0);
	class_scopes["Console"] = console_scope;

	Scope* math_scope = new Scope();
	math_scope->defineStaticFunction("sin",1);
	math_scope->defineStaticFunction("cos",1);
	math_scope->defineStaticFunction("tan",1);
	math_scope->defineStaticFunction("asin",1);
	math_scope->defineStaticFunction("acos",1);
	math_scope->defineStaticFunction("atan2",2);
	math_scope->defineStaticFunction("exp",1);
	math_scope->defineStaticFunction("log",1);
	math_scope->defineStaticFunction("log10",1);
	math_scope->defineStaticFunction("sqrt",1);
	math_scope->defineStaticFunction("min",2);
	math_scope->defineStaticFunction("max",2);
	math_scope->defineStaticFunction("pow",2);
	math_scope->defineStaticFunction("random",0);
	math_scope->defineStaticFunction("random",2);
	math_scope->defineStaticFunction("abs",1);
	math_scope->defineStaticFunction("pi",0);
	math_scope->defineStaticFunction("e",0);
	class_scopes["Math"] = math_scope;

	Scope* gl_scope = new Scope();
	gl_scope->defineStaticFunction("LineWidth",1);
	gl_scope->defineStaticFunction("BeginLines",0);
	gl_scope->defineStaticFunction("Color3",3);
	gl_scope->defineStaticFunction("Vertex2",2);
	gl_scope->defineStaticFunction("Vertex3",3);
	gl_scope->defineStaticFunction("End",0);
	class_scopes["gl"] = gl_scope;

	constructor_scope = new Scope();
	constructor_scope->defineFunction("Array",0);
	constructor_scope->defineFunction("Array",1);
	constructor_scope->defineFunction("String",0);
	constructor_scope->defineFunction("String",1);
	constructor_scope->defineFunction("GLWindow",2);
	
	symbol("(end)");
	symbol("(name)", 0, new Name());
	symbol(":");
	symbol(";");
	symbol(",");
	symbol(")");
	symbol("]");
	symbol("}");
	symbol("else");
	symbol("in");
	symbol("of");

	symbol("(literal)", 0, new Itself());
	symbol("this", 0, new This());
	symbol("super", 0, new Super());

	assignment("=");
	assignment("+=");
	assignment("-=");
	assignment("*=");
	assignment("/=");
	assignment("%=");
	assignment("++",new UnaryAssignment());
	assignment("--",new UnaryAssignment());
	assignment("~=", new TildaAssignment());
	assignment("&=");
	assignment("^=");
	assignment("|=");
	assignment("<<=");
	assignment(">>=");

	constant("true", "1LL", Symbol::Integer);
	constant("false", "0LL", Symbol::Integer);
	constant("null", "var::null", Symbol::Var);
	//constant("pi", "3.141592653589793");
	//constant("Array", "[]");

	infix("~", 45, new Tilda());
	infix("|", 46, new StrictInfix(Symbol::Integer, Symbol::Var) );
	infix("^", 47, new StrictInfix(Symbol::Integer, Symbol::Var));
	infix("&", 48, new StrictInfix(Symbol::Integer, Symbol::Var) );
	infix("<<", 49, new StrictInfix(Symbol::Integer, Symbol::Var));
	infix(">>", 49, new StrictInfix(Symbol::Integer, Symbol::Var) );
	infix("+", 50, new StrictInfix(Symbol::Integer, Symbol::Double));
	infix("-", 50, new Minus()); 
	infix("*", 60, new StrictInfix(Symbol::Integer, Symbol::Double));
	infix("/", 60, new StrictInfix(Symbol::Integer, Symbol::Double));
	infix("%", 60, new StrictInfix(Symbol::Integer, Symbol::Double));
	infix("+.", 50, new StrictDoubleInfix("_p"));
	infix("-.", 50, new StrictDoubleInfix("_m"));
	infix("*.", 60, new StrictDoubleInfix("_x"));
	infix("/.", 60, new StrictDoubleInfix("_d"));
	
	infixr("===", 40, new TripleEqual());
	infixr("!==", 40, new TripleNotEqual());
	infixr("==", 40);
	infixr("!=", 40);
	infixr("<", 40);
	infixr("<=", 40);
	infixr(">", 40);
	infixr(">=", 40);
	infixr("&&", 30);
	infixr("||", 30);

	infix("?", 20, new Question());
	infix(".", 80, new Dot());
	infix("->", 80, new Arrow());
	infix("[", 80, new Square_Bracket());
	infix("{", 80, new Curly_Bracket());
	infix("(", 80, new Bracket());

	prefix("-");
	prefix("!");
	//prefix("typeof");
	prefix("(",new Bracket());
	prefix("[", new Square_Bracket());
	prefix("{", new Curly_Bracket());
	prefix("new", new New());
    
    stmt("{", new Curly_Bracket());
	stmt("var", new Var());
	stmt("if", new If());
	stmt("return", new Return());
	stmt("break", new Break());
	stmt("while", new While());
    stmt("class", new Class());
    stmt("public", new Public());
	stmt("private", new Private());
	stmt("static", new Static());
	stmt("for", new For());
	stmt("foreach", new Foreach());
	stmt("do", new Do());
	stmt("this", new This());
	stmt("super", new Super());
}

String*	String::Instance = new String();
Integer* Integer::Instance = new Integer();
Float* Float::Instance = new Float();

bool Parser::Parse()
{
	string line;

	for (int f = 0; f < m_files.size(); f++)
	{
		ifstream cola_file (m_files[f]);

		if (cola_file.is_open())
		{
			while (!cola_file.eof() )
			{
			  getline (cola_file, line);
			  line += "\n";
			  tokenizer.Code += line;
			}
			cola_file.close();
		}
	}

	if (tokenizer.Init())
	{

	    token_nr = 0;
	    new_scope();
	    advance();
#ifndef DISPLAY_TOKENS
	    find_classes();
	    advance("(end)");
#endif
	    scope->pop();
		return true;
	}
	return false;
}

void Parser::Compile()
{
	string header;
	string line;

	ifstream object_h ("..\\Cola\\Object.h");

	if (!object_h.is_open())
    {
		throw string("could not open Object.h");
	}

	while ( line != "//code")
	{
	  getline (object_h, line);
	  header += line;
	  header += "\n";
	}

	ofstream compiled_h ("__cola.h");
	ofstream compiled_cpp ("__cola.cpp");


	if (!compiled_h.is_open())
    {
		throw string("could not open __cola.h");
	}

	if (!compiled_cpp.is_open())
	{
		throw string("could not open __cola.cpp");
	}

	compiled_h << header;

	map<string,Symbol*>::iterator i;	
	for (i = virtual_scope->def.begin(); i != virtual_scope->def.end(); i++)
	{
		FunctionName* t = static_cast<FunctionName*>(i->second);
		if (!t->reserved)
		{
			map<int,bool>::iterator j;
			for (j = t->args.begin(); j != t->args.end(); j++ )
			{
				compiled_h << "\tvirtual var ";
				compiled_h << i->first;
				compiled_h << "(";	

				def_code += "var Object::";
				def_code += i->first;
				def_code += "(";
				if (j->first)
				{
					for (int k = 0; k < j->first - 1; k++)
					{
						compiled_h << "var,";
						def_code += "var,";
					}
					compiled_h << "var";
					def_code += "var";
				}
				compiled_h << ");\n";
				def_code += ") { throw exception(); return 0LL; }\n";
			}
		}
	}
	while (!object_h.eof() )
	{
	  getline (object_h,line);
	  compiled_h << line << "\n";
	}
	object_h.close();

	compiled_cpp << "#include \"__cola.h\"\n";
	compiled_cpp << "#include \"..//Cola//Console.h\"\n";
	compiled_cpp << "#include \"..//Cola//Math.h\"\n";
	compiled_cpp << "#include \"..//Cola//Array.h\"\n";
	compiled_cpp << "#include \"..//Cola//String.h\"\n";
	compiled_cpp << "#include \"..//Cola//OpenGL.h\"\n";

	map<string,Token*>::iterator k;
	for (k = not_declared.begin(); k != not_declared.end(); k++)
	{
		if (k->second)
		compiled_cpp << k->second->compile();
	}
	compiled_cpp << def_code;

	compiled_cpp << "int main(int argc, char* argv[]) { srand ( time(NULL) ); Array* args = new Array(); for (int i = 0; i < argc; i++) args->push(new String(argv[i])); try { new Main(args); } catch (exception e) { cout << e.what(); } return 0; }\n";

    compiled_cpp.close();
	compiled_h.close();
}

void Parser::Build()
{
	string command = "cl /Zi /nologo /W3 /WX- /O2 /Oi /Oy- /GL /D _MBCS /Gm- /EHsc /MD /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Gd /analyze- /errorReport:queue /Fe";
	command += m_exe;
	command += " __cola.cpp cola.lib";
	system(command.c_str());
}

int main(int argc, char *argv[])
{
	int c = 1;
	vector<string> files;
	string exe = "cola";
	
	while (c < argc)
	{
		if (argv[c][0] == '-')
		{
			if (argv[c][1] == 't')
			{
				exe = argv[++c];
				++c;
			}
			else
			{
				cout << "bad command option\n cl [-t targetname] files...";
				return 0;
			}
		}
		else
		{
			files.push_back(argv[c]);
			++c;
		}
	}

	Parser p(files,exe);
	try
	{
		if (p.Parse())
		{
#ifndef DISPLAY_TOKENS
			p.Compile();
			p.Build();	
#endif
		}
	}
	catch (string& ex)
	{
		cout << ex << "\n";
		cin >> c;
	}

#ifdef DISPLAY_TOKENS
	cin >> c;
#endif
	return 0;
}